using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;

namespace WebDiagnostics
{
    public class DiagnoserFactory
    {
        public static DiagnoserFactory Current
        {
            get
            {
                _current = _current ?? new DiagnoserFactory();
                return _current;
            }
            internal set { _current = value; }
        }
        private static DiagnoserFactory _current;

        protected IEnumerable<DiagnoserDescriptor> Diagnosers
        {
            get
            {
                if (_diagnosers == null)
                    _diagnosers = DiscoverDiagnosers().OrderBy(x => x.Priority);

                return _diagnosers;
            }
            set { _diagnosers = value; }
        }
        private IEnumerable<DiagnoserDescriptor> _diagnosers;

        public IEnumerable<DiagnoserDescriptor> GetDiagnosers()
        {
            return Diagnosers;
        }

        public IEnumerable<IDiagnoser> GetDiagnosersByKey(IEnumerable<string> diagnosersToExecute)
        {
            IEnumerable<IDiagnoser> diagnosers =
                from diagnoser in Diagnosers
                where diagnosersToExecute.Contains(diagnoser.Key) 
                select (IDiagnoser)Activator.CreateInstance(diagnoser.DiagnoserType, diagnoser.Arguments);
            
            return diagnosers;
        }

        internal static IEnumerable<DiagnoserDescriptor> DiscoverDiagnosers()
        {
            IEnumerable<Assembly> referencedAssemblies = 
                BuildManager.GetReferencedAssemblies().Cast<Assembly>();

            return DiscoverDiagnosers(referencedAssemblies);

        }

        public static IEnumerable<DiagnoserDescriptor> DiscoverDiagnosers(IEnumerable<Assembly> assemblies)
        {
            IEnumerable<Type> assemblyTypes = assemblies.SelectMany(x => x.GetTypes());

            IEnumerable<DiagnoserDescriptor> diagnoserTypes =
                from type in assemblyTypes
                where !type.IsAbstract
                      && !type.IsInterface
                      && type != typeof(AttributedDiagnoserDescriptor)
                      && typeof(DiagnoserDescriptor).IsAssignableFrom(type)
                select (DiagnoserDescriptor)Activator.CreateInstance(type);

            IEnumerable<Type> alreadyDiscoveredDiagnoserTypes =
                diagnoserTypes.Select(x => x.DiagnoserType);

            IEnumerable<DiagnoserDescriptor> diagnoserDescriptorsFromAttributes =
                from type in assemblyTypes
                where !type.IsAbstract
                      && !type.IsInterface
                      && typeof(IDiagnoser).IsAssignableFrom(type)
                      && !alreadyDiscoveredDiagnoserTypes.Contains(type)
                let attribute = type
                    .GetCustomAttributes(typeof(DiagnoserDescriptorAttribute), false)
                    .Cast<DiagnoserDescriptorAttribute>()
                    .SingleOrDefault()
                where attribute != null
                select new AttributedDiagnoserDescriptor(type, attribute);

            return diagnoserDescriptorsFromAttributes.Union(diagnoserTypes).ToArray();
        }
    }
}